package com.tg.app.widget;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;

import androidx.core.view.GestureDetectorCompat;

import com.alibaba.fastjson.JSON;
import com.tg.app.camera.AVIOCTRLDEFs;
import com.tg.app.listener.OnZoomViewListener;
import com.tg.appcommon.android.TGLog;

/**
 * Describe:
 * Created by Rance Rang on 2019-06-20.
 * <p>
 * Copyright (c) Tange.Inc All rights reserved.
 */
public class ZoomPanTextureView extends TextureView {
    private GestureDetectorCompat panDetector;
    private ScaleGestureDetector scaleDetector;
    private int fitWidth, fitHeight;
    private PointF fitZoom = new PointF(1, 1);
    private PointF pan = new PointF(0, 0);
    private float zoom = 1;
    private float minZoom = 1.0f;
    private float maxZoom = 4.0f;
    private OnZoomViewListener onZoomViewListener;
    private PtzControlView.OnPtzControlTouchListener onPtzControlTouchListener;
    private boolean isSendPTZCmd = false;
    private int ptzCmd = -1;
    private float lastX, lastY;
    private boolean isDrag = false;
    private int mParentHeight;
    private int mParentWidth;
    private boolean isScale;
    private float analogZoom = 1.0f;
    private  ValueAnimator valueAnimator;
    public void setDrag(boolean drag) {
        isDrag = drag;
    }


    public void setSendPTZCmd(boolean sendPTZCmd) {
        isSendPTZCmd = sendPTZCmd;
    }

    public void setOnPtzControlTouchListener(PtzControlView.OnPtzControlTouchListener onPtzControlTouchListener) {
        this.onPtzControlTouchListener = onPtzControlTouchListener;
    }

    public void setOnZoomViewListener(OnZoomViewListener onZoomViewListener) {
        this.onZoomViewListener = onZoomViewListener;
    }

    //******************************************************************************
    // ZoomPanTextureView
    //******************************************************************************
    public ZoomPanTextureView(Context context) {
        super(context);
        initialize(context);
    }

    //******************************************************************************
    // ZoomPanTextureView
    //******************************************************************************
    public ZoomPanTextureView(Context context, final AttributeSet attrs) {
        super(context, attrs);
        initialize(context);
    }

    //******************************************************************************
    // ZoomPanTextureView
    //******************************************************************************
    public ZoomPanTextureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize(context);
    }

    //******************************************************************************
    // initialize
    //******************************************************************************
    private void initialize(Context context) {
        // create the gesture recognizers
        panDetector = new GestureDetectorCompat(context, new PanListener());
        scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    //******************************************************************************
    // onTouchEvent
    //******************************************************************************
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (zoom == 1) {
            float x = event.getRawX();
            float y = event.getRawY();
//            LogUtils.d("getRawX " + getTop() + " getRawY:" + getLeft());
            if (event.getAction() == MotionEvent.ACTION_UP) {
                if (onPtzControlTouchListener != null) {
                    if (isSendPTZCmd) {
                        onPtzControlTouchListener.onStopCmd();
                    } else {
                        onPtzControlTouchListener.onShortCmd(ptzCmd);
                    }
                    isSendPTZCmd = false;
                    ptzCmd = -1;
                }
            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
                float dx = x - lastX;
                float dy = y - lastY;
                if (onPtzControlTouchListener == null && isDrag) {
                    viewTranslation(dx, dy);
                }

            }
            lastX = x;
            lastY = y;
        }
        boolean fa = panDetector.onTouchEvent(event) || scaleDetector.onTouchEvent(event) || super.onTouchEvent(event);
        return true;
    }

    public void viewTranslation(float dx, float dy) {

        float leftX = getLeft() + getTranslationX();
        float topY = getTop() + getTranslationY();
        float bottomY = getBottom() + getTranslationY();
        if (leftX < 0 && dx < 0) {//left
            dx = 0;
        }
        if (getTranslationX() > 0 && dx > 0) {//右
            dx = 0;
        }
        if (topY < 0 && dy < 0) {//top
            dy = 0;
        }
        if (getParent() instanceof View) {
            View view = (View) getParent();
            if (view.getHeight() < bottomY && dy > 0) {
                dy = 0;
            }
        }
        setTranslationX(getTranslationX() + dx);
        setTranslationY(getTranslationY() + dy);
        invalidate();
    }

    //******************************************************************************
    // setZoomRange
    //******************************************************************************
    public void setZoomRange(float minZoom, float maxZoom) {
        this.minZoom = minZoom;
        this.maxZoom = maxZoom;
    }

    //******************************************************************************
    // setVideoSize
    //******************************************************************************
    public void setVideoSize(int videoWidth, int videoHeight) {
        if (isScale) return;
        // get the aspect ratio
        float aspectRatio = (float) videoHeight / videoWidth;

        // get the view size
        int viewWidth = getWidth();
        int viewHeight = getHeight();

        // get the fitted size
        if (viewHeight > (int) (viewWidth * aspectRatio)) {
            fitWidth = viewWidth;
            fitHeight = (int) (viewWidth * aspectRatio);
        } else {
            fitWidth = (int) (viewHeight / aspectRatio);
            fitHeight = viewHeight;
        }
        TGLog.d(TAG,"Zoom fitHeight " + fitHeight + " videoWidth:" + videoWidth + " videoHeight: " + videoHeight);
        TGLog.d(TAG,"Zoom fitHeight " + fitHeight + " viewWidth:" + viewWidth + " viewHeight: " + viewHeight);
        // get the fitted zoom
        fitZoom.x = (float) fitWidth / viewWidth;
        fitZoom.y = (float) fitHeight / viewHeight;
        getParentView();
        // clear the transform
        setZoomPan(1, 0, 0);
    }

    //******************************************************************************
    // setZoom
    //******************************************************************************
    public void setZoom(float newZoom) {
        zoom = newZoom;
        checkPan();
        setTransform();
    }

    public void analogZoom(float startZoom ,float endZoom,int duration,boolean isAnimation){
        TGLog.d(TAG,"analogZoom endZoom:"+endZoom+" "+ JSON.toJSONString(isAnimation));
        if (analogZoom==endZoom)return;
        if (isAnimation){
            if (valueAnimator!=null&&valueAnimator.isRunning()){
                valueAnimator.pause();
                valueAnimator.cancel();
            }
            valueAnimator = ValueAnimator.ofFloat(startZoom, endZoom);
            valueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }
                @Override
                public void onAnimationEnd(Animator animation) {

                    if (analogZoom == 1.0f) {
                        setTransform();
                    }
                }
                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
            valueAnimator.addUpdateListener(animation -> {
                float v = (float) animation.getAnimatedValue();
                analogZoomTransform(v);

            });
            valueAnimator.setDuration(duration);
            valueAnimator.start();
        }else {
            analogZoomTransform(endZoom);
        }
        analogZoom = endZoom;
    }
    public void analogZoom(float newZoom,int duration,boolean isAnimation) {
        // get the view size
        if (analogZoom==newZoom)return;
        analogZoom(analogZoom,newZoom,duration,isAnimation);

    }
    private void analogZoomTransform(float v){
        int viewWidth = getWidth();
        int viewHeight = getHeight();
        Matrix transform = new Matrix();
        transform.postScale(v, v, viewWidth / 2, viewHeight / 2);
        setTransform(transform);
        invalidate();
    }


    //******************************************************************************
    // setPan
    //******************************************************************************
    public void setPan(PointF newPan) {
        pan = newPan;
        checkPan();
        setTransform();
    }

    //******************************************************************************
    // setPan
    //******************************************************************************
    public void setPan(float newPanX, float newPanY) {
        setPan(new PointF(newPanX, newPanY));
    }

    //******************************************************************************
    // setZoomPan
    //******************************************************************************
    public void setZoomPan(float newZoom, PointF newPan) {
        zoom = newZoom;
        pan = newPan;
        checkPan();
        setTransform();
    }

    //******************************************************************************
    // setZoomPan
    //******************************************************************************
    public void setZoomPan(float newZoom, float newPanX, float newPanY) {
        setZoomPan(newZoom, new PointF(newPanX, newPanY));
    }

    //******************************************************************************
    // checkPan
    //******************************************************************************
    private void checkPan() {
        PointF maxPan = getMaxPan();
        if (maxPan.x == 0) pan.x = 0;
        else if (pan.x < -maxPan.x) pan.x = -maxPan.x;
        else if (pan.x > maxPan.x) pan.x = maxPan.x;

        if (maxPan.y == 0) pan.y = 0;
        else if (pan.y < -maxPan.y) pan.y = -maxPan.y;
        else if (pan.y > maxPan.y) pan.y = maxPan.y;
    }

    //******************************************************************************
    // getMaxPan
    //******************************************************************************
    private PointF getMaxPan() {
        int viewWidth = getWidth();
        int viewHeight = getHeight();
        return new PointF(Math.max(Math.round((fitWidth * zoom - viewWidth) / 2), 0),
                Math.max(Math.round((fitHeight * zoom - viewHeight) / 2), 0));
    }

    //******************************************************************************
    // setTransform
    //******************************************************************************
    private void setTransform() {
        // get the view size
        int viewWidth = getWidth();
        int viewHeight = getHeight();

        // scale relative to the center
        Matrix transform = new Matrix();
        float sx = fitZoom.x * zoom;
        float sy = fitZoom.y * zoom;
        TGLog.i(TAG,"Zoom width zoom: " + zoom + " fitZoom.x: " + fitZoom.x + " fitZoom.y:" + fitZoom.y + " zoom.x: " + sx + " zoom.y: " + sy);
        int width = (int) (fitWidth * sx);
        int height = (int) (fitHeight * sy);

        float dx = pan.x;
        float dy = pan.y;
        if (fitWidth != mParentWidth) {
            if (width <= mParentWidth) {
                dx = 0.0f;
                sx = 1.0f;
            } else {
                float x = width * 1.0f / viewWidth;
                sx = x;
            }
        }

        if (fitHeight != mParentHeight) {
            if (height <= mParentHeight) {
                dy = 0.0f;
                sy = 1.0f;
            } else {
                float y = height * 1.0f / viewHeight;
                sy = y;
            }
        }
        if (analogZoom > 1.0f) {
            sx = sx * analogZoom;
            sy = sy * analogZoom;
        }
//        LogUtils.d("Zoom width fitWidth: " + fitWidth + " mParentHeight: " + mParentHeight + " mParentWidth:" + mParentWidth);
//        LogUtils.d("Zoom width: " + width + " height: " + height + " dx:" + dx + " dy: " + dy + " zoom.x: " + sx + " zoom.y: " + sy + " analogZoom: " + analogZoom);
        transform.postScale(sx, sy, viewWidth / 2, viewHeight / 2);

        // add the panning
        if (pan.x != 0 || pan.y != 0) {
            ViewGroup.LayoutParams v = getLayoutParams();
            v.height = height;
            v.width = width;
            setLayoutParams(v);
            transform.postTranslate(dx, dy);
        }
        // set the transform
        setTransform(transform);
        invalidate();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        getParentView();
    }

    private void getParentView() {
        ViewGroup mViewGroup = (ViewGroup) getParent();
        if (null != mViewGroup) {
            mParentWidth = mViewGroup.getWidth();
            mParentHeight = mViewGroup.getHeight();
//            LogUtils.d("Zoom onMeasure mParentWidth:" + mParentWidth + " mParentHeight:" + mParentHeight);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////
    // PanListener
    ////////////////////////////////////////////////////////////////////////////////
    private class PanListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (e2.getPointerCount() == 1) {
                if (zoom > 1) {
                    setPan(pan.x - distanceX, pan.y - distanceY);
                    return true;
                } else if (zoom == 1 && onPtzControlTouchListener != null) {
                    int ptz = getEventDirection(e1, e2);
                    if (ptzCmd == -1) {
                        ptzCmd = ptz;
                        onPtzControlTouchListener.onLongCmd(ptzCmd);
                    } else if (ptz != ptzCmd) {
                        ptzCmd = ptz;
                        onPtzControlTouchListener.onStopCmd();
                        onPtzControlTouchListener.onLongCmd(ptzCmd);
                    }
                }
            }

            return false;
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            if (onZoomViewListener != null) {
                onZoomViewListener.onSingleClick(false);
            }
            return super.onSingleTapConfirmed(e);
        }


    }


    // 180°分成4个刻度，每个刻度为45°
    private final float DEGREE_UNIT = 45.0f;
    public static final String TAG = "getEventDirection";

    /**
     * 判断当前的移动方向
     *
     * @param e1
     * @param e2
     */
    private int getEventDirection(MotionEvent e1, MotionEvent e2) {
        int event = -1;

        float x1 = e1.getX();
        float y1 = e1.getY();
        float x2 = e2.getX();
        float y2 = e2.getY();
        float a = x2 - x1;// a边为对边 x2-x1
        float b = y2 - y1;// b边为邻边 y2-y1
        // 反正切得到∠B弧度
        double B = Math.atan(a / b);
        // 弧度转换成角度
        B = Math.toDegrees(B);
        // 以x轴为分界线，以180°计算，算出的角度如果为负数，+180
        B = B < 0 ? B + 180 : B;
        if (Math.abs(a) >= 15 || Math.abs(b) >= 15) {
            // 当手指往x轴的正方向移动的时候，y轴正方向为0°，反方向为180°
            if (x2 > x1) {
                if (B < DEGREE_UNIT) {
                    event = AVIOCTRLDEFs.AVIOCTRL_PTZ_UP;  // AVIOCTRL_PTZ_DOWN
                } else if (B >= DEGREE_UNIT && B < DEGREE_UNIT * 3) {
                    event = AVIOCTRLDEFs.AVIOCTRL_PTZ_LEFT;  //AVIOCTRL_PTZ_LEFT
                } else if (B >= DEGREE_UNIT * 3 && B < DEGREE_UNIT * 5) {
                    event = AVIOCTRLDEFs.AVIOCTRL_PTZ_DOWN;
                }
            } else if (x2 < x1) {
                // 当手指往x轴的反方向移动的时候y轴反方向为0°，正方向为180°
                if (B < DEGREE_UNIT) {
                    event = AVIOCTRLDEFs.AVIOCTRL_PTZ_DOWN;
                } else if (B >= DEGREE_UNIT && B < DEGREE_UNIT * 3) {
                    event = AVIOCTRLDEFs.AVIOCTRL_PTZ_RIGHT;
                } else if (B >= DEGREE_UNIT * 3 && B < DEGREE_UNIT * 5) {
                    event = AVIOCTRLDEFs.AVIOCTRL_PTZ_UP;
                }
            } else {
                // 如果正好x轴不动，y轴移动
                if (y2 > y1) {
                    event = AVIOCTRLDEFs.AVIOCTRL_PTZ_UP;
                } else if (y2 < y1) {
                    event = AVIOCTRLDEFs.AVIOCTRL_PTZ_DOWN;
                }
            }
        }
        return event;
    }


    ////////////////////////////////////////////////////////////////////////////////
    // ScaleListener
    ////////////////////////////////////////////////////////////////////////////////
    private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener {
        private float startZoom = 1;
        private PointF center = new PointF();

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float newZoom = startZoom * detector.getScaleFactor();
            newZoom = Math.max(minZoom, Math.min(newZoom, maxZoom));
            if (newZoom != zoom) {
                PointF offset = new PointF(detector.getFocusX() - center.x, detector.getFocusY() - center.y);
                PointF focus = new PointF((offset.x - pan.x) / zoom, (offset.y - pan.y) / zoom);
                setZoomPan(newZoom, offset.x - focus.x * newZoom, offset.y - focus.y * newZoom);

                if (onZoomViewListener != null) {
                    onZoomViewListener.onScale(newZoom);
                }
            }
            return false;
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            isScale = true;
            startZoom = zoom;
            center.x = getWidth() / 2;
            center.y = getHeight() / 2;
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            isScale = false;
        }
    }
}